using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BardsTaleMapViewer {
  public class Huffman {
    public int _unk04;
    public byte [] _tree;
    public int _offsetTree;

    //public byte [] _bufferTree;
    //public byte [] _bufferData;
    //public int _offsetBufferTree;
    //public int _offsetBufferData;
    public int _currentMask;
    public int _currentValue;
    public Computer _system;

    public Huffman (byte [] bufferTree, ref int offsetBufferTree, Computer system) {
      _system = system;
      InitDecoder (bufferTree, ref offsetBufferTree);
    }

    public byte [] Decode_Amiga (byte [] bufferData, ref int offsetBufferData, int nbBytes) {
      byte [] bufferDst = new byte [nbBytes];
      int offsetDst = 0;

      for (; ; ) {
        if (offsetDst == nbBytes)
          break;
        int offsetTree = 0;
        for (; ; ) {
          if (_tree.ReadLong (offsetTree) == 0xFFFFFFFF)
            break;
          if (!NextBit_Amiga (bufferData, ref offsetBufferData))
            offsetTree = (int) _tree.ReadLong (offsetTree);
          else
            offsetTree = (int) _tree.ReadLong (offsetTree + 4);
        }
        bufferDst.WriteByte (offsetDst, _tree.ReadByte (offsetTree + 8));
        offsetDst++;
      }

      return (bufferDst);
    }

    public byte [] Decode_Atari (byte [] bufferData, ref int offsetBufferData, int nbBytes) {
      //_currentMask = 0x80;
      //_currentValue = bufferData.ReadByte (ref offsetBufferData);

      byte [] bufferDst = new byte [nbBytes];
      int offsetDst = 0;

      for (; ; ) {
        if (offsetDst == nbBytes)
          break;
        int offsetTree = 0;
        for (; ; ) {
          if (_tree.ReadLong (offsetTree) == 0xFFFFFFFF)
            break;
          if (!NextBit_Atari (bufferData, ref offsetBufferData))
            offsetTree = (int) _tree.ReadLong (offsetTree);
          else
            offsetTree = (int) _tree.ReadLong (offsetTree + 4);
        }
        bufferDst.WriteByte (offsetDst, _tree.ReadByte (offsetTree + 8));
        offsetDst++;
      }

      return (bufferDst);
    }

    private void InitDecoder (byte [] bufferTree, ref int offsetBufferTree) {
      //_bufferTree = bufferTree;
      //_offsetBufferTree = offsetBufferTree;
      _tree = new byte [7690];
      for (int i = 0; i < 7690; i += 10) {
        _tree.WriteLong (i + 0, 0xFFFFFFFF);
        _tree.WriteLong (i + 4, 0xFFFFFFFF);
      }
      _offsetTree = 10;

      if (_system == Computer.Amiga) {
        _currentMask = 0x8000;
        _currentValue = bufferTree.ReadWord (ref offsetBufferTree);
        InitDecoder_Recursive_Amiga (bufferTree, ref offsetBufferTree, 0);
      }
      else {
        _currentMask = 0x80;
        _currentValue = bufferTree.ReadByte (ref offsetBufferTree);
        InitDecoder_Recursive_Atari (bufferTree, ref offsetBufferTree, 0);
      }

      // remove first
      //int start = 10;
      //for (int i = 0; i < 7680; i += 10) {
      //  long a = _tree.ReadLong (i + 0 + start);
      //  if (a != 0xFFFFFFFF)
      //    a -= 10;
      //  long b = _tree.ReadLong (i + 4 + start);
      //  if (b != 0xFFFFFFFF)
      //    b -= 10;
      //  int c = _tree.ReadByte (i + 8 + start);
      //  _tree.WriteLong (i + 0, a);
      //  _tree.WriteLong (i + 4, b);
      //  _tree.WriteByte (i + 8, c);
      //  _tree.WriteLong (i + 0 + start, 0xFFFFFFFF);
      //  _tree.WriteLong (i + 4 + start, 0xFFFFFFFF);
      //  _tree.WriteByte (i + 8 + start, 0);
      //}
    }

    private void InitDecoder_Recursive_Amiga (byte [] bufferTree, ref int offsetBufferTree, int offsetTree) {
      if (!NextBit_Amiga (bufferTree, ref offsetBufferTree)) {
        int prevOffsetTree = AdvanceToNext ();
        if (_tree.ReadLong (offsetTree + 4) != 0xFFFFFFFF)
          throw new Exception ();
        _tree.WriteLong (offsetTree + 4, prevOffsetTree);

        prevOffsetTree = AdvanceToNext ();
        if (_tree.ReadLong (offsetTree + 0) != 0xFFFFFFFF)
          throw new Exception ();
        _tree.WriteLong (offsetTree + 0, prevOffsetTree);

        InitDecoder_Recursive_Amiga (bufferTree, ref offsetBufferTree, prevOffsetTree);
        NextBit_Amiga (bufferTree, ref offsetBufferTree);
        InitDecoder_Recursive_Amiga (bufferTree, ref offsetBufferTree, (int) _tree.ReadLong (offsetTree + 4));
      }
      else
        _tree.WriteByte (offsetTree + 8, NextByte_Amiga (bufferTree, ref offsetBufferTree));
    }

    private void InitDecoder_Recursive_Atari (byte [] bufferTree, ref int offsetBufferTree, int offsetTree) {
      if (!NextBit_Atari (bufferTree, ref offsetBufferTree)) {
        int prevOffsetTree = AdvanceToNext ();
        if (_tree.ReadLong (offsetTree) != 0xFFFFFFFF)
          throw new Exception ();
        _tree.WriteLong (offsetTree, prevOffsetTree);
        InitDecoder_Recursive_Atari (bufferTree, ref offsetBufferTree, prevOffsetTree);

        NextBit_Atari (bufferTree, ref offsetBufferTree);

        prevOffsetTree = AdvanceToNext ();
        if (_tree.ReadLong (offsetTree + 4) != 0xFFFFFFFF)
          throw new Exception ();
        _tree.WriteLong (offsetTree + 4, prevOffsetTree);
        InitDecoder_Recursive_Atari (bufferTree, ref offsetBufferTree, prevOffsetTree);
      }
      else
        _tree.WriteByte (offsetTree + 8, NextByte_Atari (bufferTree, ref offsetBufferTree));
    }

    private int AdvanceToNext () {
      _offsetTree += 10;
      if (_tree.ReadLong (_offsetTree - 10 + 0) != 0xFFFFFFFF)
        throw new Exception ();
      _tree.WriteLong (_offsetTree - 10 + 0, 0xFFFFFFFF);
      if (_tree.ReadLong (_offsetTree - 10 + 4) != 0xFFFFFFFF)
        throw new Exception ();
      _tree.WriteLong (_offsetTree - 10 + 4, 0xFFFFFFFF);
      return (_offsetTree - 10);
    }

    private bool NextBit_Amiga (byte [] buffer, ref int offset) {
      int d0 = _currentValue;
      d0 &= _currentMask;
      _currentMask >>= 1;
      if (_currentMask == 0) {
        _currentMask = 0x8000;
        if ((offset + 2) > buffer.Length)
          d0 = d0;
        //Console.WriteLine ("EOF Amiga");
        else
          _currentValue = buffer.ReadWord (ref offset);
      }
      return (d0 != 0);
    }

    private bool NextBit_Atari (byte [] buffer, ref int offset) {
      int d0 = _currentValue;
      d0 &= _currentMask;
      _currentMask >>= 1;
      if (_currentMask == 0) {
        _currentMask = 0x80;
        if ((offset + 1) > buffer.Length)
          d0 = d0;
        //Console.WriteLine ("EOF Atari");
        else
          _currentValue = buffer.ReadByte (ref offset);
      }
      return (d0 != 0);
    }

    private int NextByte_Amiga (byte [] bufferTree, ref int offsetBufferTree) {
      int b = 0;
      for (int i = 0; i < 8; i++) {
        b <<= 1;
        if (NextBit_Amiga (bufferTree, ref offsetBufferTree))
          b |= 1;
      }
      return (b);
    }

    private int NextByte_Atari (byte [] bufferTree, ref int offsetBufferTree) {
      int b = 0;
      for (int i = 0; i < 8; i++) {
        b <<= 1;
        if (NextBit_Atari (bufferTree, ref offsetBufferTree))
          b |= 1;
      }
      return (b);
    }
  }

  public enum Computer {
    Amiga,
    Atari
  }
}
